#ifndef T_FILE_HPP
#define T_FILE_HPP

#define PLATFORM_SERVER

#include <string>
#include <vector>
#include <fstream>

class TFile {
public:
	// check if this is a file (0) or a dir(1). If not exist, it is -1
	static int isFileOrDir(std::wstring filenameIn);
	static int countFileDirNumberInDir(std::wstring dirnameIn);

	static void initializeDirForSWSIMobileDev(std::string imageFolder);

	static void sortFileNamesWithNum(std::vector<std::wstring>& filesInOut, std::vector<std::wstring>* filesSortedOutOpt = nullptr);

	static std::wstring searchForFile(std::wstring filenameFormat);

	static std::vector<std::wstring> listFiles(std::wstring filenamePattern);
	static void listFiles(std::wstring filenamePattern, std::vector<std::wstring>& files);
	// record the image with suffix "bmp" "jpg" "jpeg"
	static void listFiles(std::wstring folderName, std::wstring filenamePattern, std::vector<std::wstring>& files);
	// record the image filename with preFix in sequence to the files vector
	static void listImageFiles(std::wstring imageFolder, std::vector<std::wstring>& files, bool sorted = true);
	static void deleteFile(std::wstring filename);
	static void deleteFiles(std::wstring folderName, std::wstring filenamePattern);
	static void copyFile(std::wstring src, std::wstring dst, bool flagSkipIfSameSize = false);
	static void renameFile(std::wstring oldName, std::wstring newName);

	// the lowest node is ignored unless ends with "/"
	static void makeDirTreeIfNeeded(std::wstring fullPath);

	// make the dir if needed. then clean it
	static void clearDir(std::wstring folderName, std::wstring filenamePattern = L"*");

	static std::string resolveShortcutFile(std::string mayBeShortCutFileName);

	static std::string renameFile(std::string filenameWithFullDir, std::string outputDirWithSlash, std::string outputSurfix, bool includeParentDirInName);

	static std::wstring addSlashIfNeeded(std::wstring dirName);

	// not tested if it is a full name with dir or if there is no surfix
	static std::wstring getFilenameNoDirOnly(std::wstring filenameFullIn, std::wstring& surfixOut);
	static std::wstring getFilenameNoDirOnly(std::wstring filenameFullIn);
	static std::wstring getDirNoFilenameOnly(std::wstring filenameFullIn);
	static std::vector<std::wstring> splitFilenameWithDirAndSurfix(std::wstring filenameFullIn);
	static std::string getBottomDirName(std::string filenameFullIn);
	static std::wstring makeFilenameByAppend(std::wstring refFilenameIn, std::wstring appendStr);
	static std::wstring getExeDir();
	static std::wstring getCodeDir();

	static std::string replaceHiphenWithUnderscore(std::string stringIn);
	static std::string replaceInvalidCharForFilenameWithUnderscore(std::string stringIn);

	static bool isFilenameJPEG(std::string filenameIn);

	// rename old file up to keepBUNum copies named as _buX
// refreshImg = true is less efficient but refreshes image preview
	static std::wstring backupFile(std::wstring filenameIn, int keepBUNum = 1, bool refreshImg = false);

private:
	static size_t findNextSlash(std::wstring& strIn, size_t offset);

};


class TWrite2Bin {
public:
	static std::vector<uint8_t> mixTextAndBin(std::string& text, std::vector<uint8_t>& bin);
	static void mixTextAndBin(std::string& text, std::vector<uint8_t>& bin, std::wstring filename);

	static void combineArrays(std::vector<uint8_t>& array0Out,
		std::vector<uint8_t>* array1,
		std::vector<uint8_t>* array2 = nullptr,
		std::vector<uint8_t>* array3 = nullptr,
		std::vector<uint8_t>* array4 = nullptr);

	template<class Type>
	static void write(std::ofstream& fileOut, Type data) {
		fileOut.write(reinterpret_cast<char*>(&data), sizeof(Type));
	}

	template<class Type>
	static void writeArray(std::ofstream& fileOut, Type* data, int length) {
		fileOut.write(reinterpret_cast<char*>(data), sizeof(Type) * length);
	}

	template<class Type>
	static void writeArray2Bin(Type* data, int length, std::string filename) {
		std::ofstream myfile;
		myfile.open(filename, std::ios::out | std::ios::binary);
		write<int>(myfile, length);
		myfile.write(reinterpret_cast<char*>(data), sizeof(Type) * length);
		myfile.close();
	}

	template<class Type>
	static void write2DArray2Bin(Type* data, int w, int h, std::string filename) {
		std::ofstream myfile;
		myfile.open(filename, std::ios::out | std::ios::binary);
		write<int>(myfile, w);
		write<int>(myfile, h);
		myfile.write(reinterpret_cast<char*>(data), sizeof(Type) * w * h);
		myfile.close();
	}

	static bool writeByteStream(std::wstring filename, char* byteStream, size_t streamLen);
	static bool writeByteStream(std::wstring filename, uint8_t* byteStream, size_t streamLen);
	static bool writeByteStream(std::wstring filename, void* byteStream, size_t streamLen);

	static void writeDb(std::ofstream& fileOut, float data);
	static void writeDbArray2Bin(std::vector<float> data, std::string filename);
	static void writeDbArray2Bin(float* data, int length, std::string filename);
	static void writeDbArrayArrayEqualLen2Bin(std::vector<std::vector<float>>& data, std::string filename);

	static void writeInt(std::ofstream& fileOut, int data);
	static void writeIntArray2Bin(int* data, int length, std::string filename);

	static void writeBool(std::ofstream& fileOut, bool data);
	static void writeBoolArray2Bin(bool* data, int width, int height, std::string filename);
};

// https://stackoverflow.com/questions/11563963/writing-a-binary-file-in-c-very-fast
class TFileStreamer {
public:
	TFileStreamer();
	TFileStreamer(std::string filename, bool trueForWriteFalseForRead);
	~TFileStreamer();

	void openFile(std::string filename, bool trueForWriteFalseForRead);
	void write(std::vector<uint8_t>& arrayIn);
	void write(const void* dataPtr, int length);
	void write(int data);
	void write(std::vector<int>& data);
	int getLength();
	void read(int lengthIn, std::vector<uint8_t>& arrayOut);
	void read(int lengthIn, void* arrayAllocatedOut);
	std::string readStr(int lengthIn, void* arrayAllocatedOut);
	void gotoPos(size_t goToOffset);
	void flush();
	void closeFile();
private:
	//std::fstream _stream;
	// usually fstream
	void* _stream;
	bool flagStreamOpened;
	bool flagTrueForWriteFalseForRead;
	size_t ramainingLen;
};

class TReadFromBin {
public:
	static void readByteStream(std::wstring filename, std::vector<uint8_t>& byteStream);

	static void readByteStream(std::string filename, uint8_t*& byteStream, int& streamLen);
	static void readByteStream(std::string filename, std::vector<uint8_t>& byteStream);
	static std::vector<uint8_t> readByteStream(std::string filename);
	static std::vector<uint8_t> readByteStream(std::string filename, size_t offset, size_t count);
	static void readByteStream(std::wstring filename, uint8_t** ptr, int* sizeOut);
	static std::vector<uint8_t> readByteStream(std::wstring filename);
	// data is referenced from hybrid thus need not be released explicitly
	static void splitTextAndBin(std::vector<uint8_t>& hybridTxtBin, std::string& textOut, uint8_t** dataPtr, size_t* dataLen);
	static void splitTextAndBin(void* hybridTxtBin, size_t len, std::string& textOut, uint8_t** dataPtr, size_t* dataLen);
};

class TWrite2Txt {
private:
	static std::string filenameGlobalTXT;
	std::wstring filenameTXT = std::wstring(L"Filename not assigned");
	static bool flagFilenameGlobalSet;
	bool flagFilenameSet = false;
public:
	TWrite2Txt(std::string filenameIn);
	TWrite2Txt(std::wstring filenameIn, bool flagEnable);

	static void clearFile(std::wstring filenameIn);

	void setFile(std::string filenameIn);
	static void setGlobalFile(std::string filenameIn);

	void setFile(char* filenameIn);
	static void setGlobalFile(char* filenameIn);

	void writeString(std::string newContent);
	void writeString(std::wstring newContent);
	static void writeGlobalString(std::string newContent);
	static void writeString(std::string fullContent, std::string filename, bool flagErase = false, bool flagWithTime = false);
	static void writeString(std::wstring fullContent, std::wstring filename, bool flagErase = false, bool flagWithTime = false);
	static void writeString(std::string fullContent, std::wstring filename, bool flagErase = false, bool flagWithTime = false);

	void writeLine(std::string newContent);
	void writeLine(std::wstring newContent);
	static void writeGlobalLine(std::string newContent);
	static void writeLine(std::string fullContent, std::string filename, bool flagErase = false, bool flagWithTime = false);
};

class TReadFromTxt {
public:
	static std::string readAllText(std::wstring filename);
	static std::wstring readAllWText(std::wstring filename);
	static std::vector<std::string> readAllLines(std::wstring filename);
	static std::vector<std::wstring> readAllLinesW(std::wstring filename);
	static std::string findSubstrBetween(std::string baseStr, std::string before, std::string after);
	static char findTextAfter(std::string baseStr, std::string before);
	// find first row of difference
	static int compareTwoTextFiles(std::wstring filename0, std::wstring filename1);
};

#endif